package com.skyline.energy.utils;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.ClassUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.skyline.energy.exception.DaoGenerateException;
import com.skyline.energy.parser.ParsedExpression;

public final class CommonUtils {
	private static final Log LOGGER = LogFactory.getLog(CommonUtils.class);

	private CommonUtils() {
	}

	@SuppressWarnings("unchecked")
	public static <T> T[] addElemToArray(T[] array, T element, int index) {
		if (array == null) {
			array = (T[]) Array.newInstance(element.getClass(), (index + 1));
			array[index] = element;
			return array;
		}
		if (index < array.length) {
			array[index] = element;
			return array;
		} else {
			T[] newArray = (T[]) Array.newInstance(element.getClass(), (index + 1));
			System.arraycopy(array, 0, newArray, 0, array.length);
			newArray[index] = element;
			return newArray;
		}

	}

	public static void assertIsNull(Object object, String message) {
		if (object != null) {
			throw new IllegalArgumentException(message);
		}
	}

	public static void assertNotNull(Object object, String message) {
		if (object == null) {
			throw new IllegalArgumentException(message);
		}
	}

	public static boolean isTypePage(Class<?> type) {
		return (type != null && ClassUtils.isAssignable(type, Page.class));
	}

	public static boolean isTypeArray(Class<?> type) {
		return (type != null && type.isArray());
	}

	public static boolean isTypeCollection(Class<?> type) {
		return (type != null && ClassUtils.isAssignable(type, Collection.class));
	}

	public static boolean isTypeList(Class<?> type) {
		return (type != null && ClassUtils.isAssignable(type, List.class));
	}

	public static boolean isTypePrimitive(Class<?> type) {
		return (type != null && type.isPrimitive());
	}

	public static boolean isTypeVoid(Class<?> type) {
		return (type != null && void.class.equals(type));
	}

	public static boolean isTypeNumber(Class<?> type) {
		return (type != null && ClassUtils.isAssignable(type, Number.class));
	}

	public static boolean isTypeMap(Class<?> type) {
		return (type != null && ClassUtils.isAssignable(type, Map.class));
	}

	public static Page getPageArgument(Object[] args, int pageIndex) {
		if (pageIndex != -1) {
			return (Page) args[pageIndex];
		}
		return null;
	}
	
	public static Object[] fetchVlaues(Method[] getterMethods, Integer[] parameterIndexes, Object[] args,
			List<String> parameterNames) {
		Object[] values = new Object[getterMethods.length];
		for (int i = 0; i < getterMethods.length; i++) {
			Method method = getterMethods[i];
			Integer index = parameterIndexes[i];

			Object value = fetchVlaue(method, index, args, parameterNames);
			values[i] = value;
		}
		return values;
	}

	@SuppressWarnings("rawtypes")
	public static Object fetchVlaue(Method getterMethod, Integer index, Object[] args, String paramName) {
		if (index == null || index == -1) {
			return null;
		}

		Object arg = args[index];

		if (getterMethod == null) {
			Class<?> clazz = arg.getClass();
			
			if (clazz.isEnum()) {
				return arg.toString();
			}

			if (isTypeMap(clazz)) {
				Map map = (Map) arg;
				int pos = paramName.indexOf('.');
				String prop = paramName.substring(pos + 1);
				Object result = map.get(prop);
				if (result.getClass().isEnum()) {
					return result.toString();
				}
				return result;
			}
			
			return arg;
		} else {
			try {
				Object value = getterMethod.invoke(arg, new Object[0]);
				if (value.getClass().isEnum()) {
					return value.toString();
				} else {
					return value;
				}
			} catch (Throwable e) {
				LOGGER.info("invoke method failed", e);
				return null;
			}
		}
	}

	public static Object fetchVlaue(Method getterMethod, Integer index, Object[] args, List<String> parameterNames) {
		if (index == null || index == -1) {
			return null;
		}

		String paramName = parameterNames.get(index);

		return fetchVlaue(getterMethod, index, args, paramName);
	}

	public static Object[] getGettersAndIndexes(List<String> parameterNames, Map<String, Integer> paramIndexes,
			Class<?>[] paramTypes) throws DaoGenerateException {
		int length = parameterNames.size();
		Method[] getters = new Method[length];
		Integer[] parameterIndexes = new Integer[length];
		for (int i = 0; i < length; i++) {
			String paramName = parameterNames.get(i);
			int pos = paramName.indexOf('.');
			if (pos != -1) {
				String actualName = paramName.substring(0, pos);

				int index = getParameIndex(paramIndexes, actualName);
				parameterIndexes[i] = index;

				String prop = paramName.substring(pos + 1);
				Method getter = findGetterByPropertyName(paramTypes[index], prop);
				getter.setAccessible(true); //no check to upgrade performance

				getters[i] = getter;
			} else { // don't need getters
				getters[i] = null;
				Integer index = getParameIndex(paramIndexes, paramName);
				parameterIndexes[i] = index;
			}
		}
		return new Object[] { getters, parameterIndexes };
	}

	public static Integer getParameIndex(Map<String, Integer> paramIndexes, String paramName)
			throws DaoGenerateException {
		if (!paramIndexes.containsKey(paramName)) {
			throw new DaoGenerateException("Missing @Param(\"" + paramName + "\") in parameter of method");
		}
		Integer index = paramIndexes.get(paramName);
		return index;
	}

	public static Method findGetterByPropertyName(Class<?> clazz, String prop) throws DaoGenerateException {
		if (isTypeMap(clazz)) {
			return null;
		}
		String name = Character.toUpperCase(prop.charAt(0)) + prop.substring(1);
		String getter = "get" + name;
		String is = "is" + name;
		Method[] methods = clazz.getMethods();
		for (Method method : methods) {
			Class<?> returnType = method.getReturnType();
			if (method.getParameterTypes().length == 0 && !returnType.equals(void.class)) {
				if (getter.equals(method.getName())) {
					return method;
				}
				if (is.equals(method.getName())
						&& (returnType.equals(boolean.class) || returnType.equals(Boolean.class))) {
					return method;
				}
			}
		}
		throw new DaoGenerateException("Cannot find getter of property '" + prop + "' of class: " + clazz.getName());
	}

	public static Object[] getGettersAndIndexes(List<String> parameterNames, Map<String, Integer> paramIndexes,
			Map<String, Integer> batchParamIndexes, Class<?>[] paramTypes) throws DaoGenerateException {
		int length = parameterNames.size();
		Method[] getters = new Method[length];
		Integer[] parameterIndexes = new Integer[length];
		for (int i = 0; i < length; i++) {
			String paramName = parameterNames.get(i);
			int pos = paramName.indexOf('.');
			if (pos != -1) {
				String actualName = paramName.substring(0, pos);

				Class<?> componentType = null;
				Integer index = null;

				if (!paramIndexes.containsKey(actualName) && !batchParamIndexes.containsKey(actualName)) {
					throw new DaoGenerateException("Missing @Param(\"" + actualName + "\") or @BatchParam(\""
							+ actualName + "\") in parameter of method");
				} else if (paramIndexes.containsKey(actualName)) {
					index = paramIndexes.get(actualName);
					componentType = paramTypes[index];
				} else {
					index = batchParamIndexes.get(actualName);

					Class<?> paramType = paramTypes[index];
					if (!paramType.isArray()) {
						throw new DaoGenerateException("@BatchParam(\"" + paramName
								+ "\") only can be used on an array in parameter of method");
					}
					componentType = paramTypes[index].getComponentType();
				}

				parameterIndexes[i] = index;
				String prop = paramName.substring(pos + 1);
				Method getter = findGetterByPropertyName(componentType, prop);
				getter.setAccessible(true); //no check to upgrade performance
				getters[i] = getter;

			} else { // don't need getters
				getters[i] = null;
				if (!paramIndexes.containsKey(paramName) && !batchParamIndexes.containsKey(paramName)) {
					throw new DaoGenerateException("Missing @Param(\"" + paramName + "\") or @BatchParam(\""
							+ paramName + "\")  in parameter of method");
				} else if (paramIndexes.containsKey(paramName)) {
					Integer index = paramIndexes.get(paramName);
					parameterIndexes[i] = index;
				} else {
					Integer index = batchParamIndexes.get(paramName);
					parameterIndexes[i] = index;
				}
			}
		}
		return new Object[] { getters, parameterIndexes };
	}

	public static String getSql(ParsedExpression parsedExpression) {
		String originalSql = parsedExpression.getOriginalExpression();
		StringBuilder actualSql = new StringBuilder();
		List<String> paramNames = parsedExpression.getParameterNames();
		int lastIndex = 0;
		for (int i = 0; i < paramNames.size(); i++) {
			int[] indexes = parsedExpression.getParameterIndexes(i);
			int startIndex = indexes[0];
			int endIndex = indexes[1];
			actualSql.append(originalSql.substring(lastIndex, startIndex));
			actualSql.append("?");
			lastIndex = endIndex;
		}
		actualSql.append(originalSql.substring(lastIndex, originalSql.length()));
		return actualSql.toString();
	}

	public static Object fetchArrayValue(Object array, int index) {
		Class<?> clazz = array.getClass();
		if (!clazz.isArray()) {
			throw new IllegalArgumentException("argument array must be an array");
		}
		Class<?> componentType = clazz.getComponentType();
		if (!componentType.isPrimitive()) {
			return ((Object[]) array)[index];
		}

		return fetchPrimitiveArrayValue(array, index, componentType);
	}

	private static Object fetchPrimitiveArrayValue(Object array, int index, Class<?> componentType) {
		if (long.class.equals(componentType)) {
			return ((long[]) array)[index];
		}
		if (int.class.equals(componentType)) {
			return ((int[]) array)[index];
		}
		if (boolean.class.equals(componentType)) {
			return ((boolean[]) array)[index];
		}
		if (double.class.equals(componentType)) {
			return ((double[]) array)[index];
		}
		if (char.class.equals(componentType)) {
			return ((char[]) array)[index];
		}
		if (float.class.equals(componentType)) {
			return ((float[]) array)[index];
		}
		if (byte.class.equals(componentType)) {
			return ((byte[]) array)[index];
		}
		if (short.class.equals(componentType)) {
			return ((short[]) array)[index];
		}
		return null;
	}
}
